<!DOCTYPE html>
<html class="writer-html5" lang="zh" >
<head>
  <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />

  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Ch8-1 离屏渲染 &mdash; EasyVulkan</title>
      <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
      <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
      <link rel="stylesheet" href="_static/theme.css" type="text/css" />
  <!--[if lt IE 9]>
    <script src="_static/js/html5shiv.min.js"></script>
  <![endif]-->
  
        <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
        <script src="_static/jquery.js"></script>
        <script src="_static/underscore.js"></script>
        <script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
        <script src="_static/doctools.js"></script>
    <script src="_static/js/theme.js"></script>
    <link rel="index" title="索引" href="genindex.html" />
    <link rel="search" title="搜索" href="search.html" />
    <link rel="next" title="Ch8-2 深度测试和深度可视化" href="Ch8-2%20%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%95%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%8F%AF%E8%A7%86%E5%8C%96.html" />
    <link rel="prev" title="Ch7-7 使用贴图" href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html" /> 
</head>

<body class="wy-body-for-nav"> 
  <div class="wy-grid-for-nav">
    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
      <div class="wy-side-scroll">
        <div class="wy-side-nav-search" >
            <a href="index.html" class="icon icon-home"> EasyVulkan
            <img src="_static/logo1.png" class="logo" alt="Logo"/>
          </a>
<div role="search">
  <form id="rtd-search-form" class="wy-form" action="search.html" method="get">
    <input type="text" name="q" placeholder="在文档中搜索" />
    <input type="hidden" name="check_keywords" value="yes" />
    <input type="hidden" name="area" value="default" />
  </form>
</div>
        </div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
              <p class="caption" role="heading"><span class="caption-text">第一章 初始化</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch1-0%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.html">Ch1-0 准备工作</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-1%20%E5%88%9B%E5%BB%BAGLFW%E7%AA%97%E5%8F%A3.html">Ch1-1 创建GLFW窗口</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-2%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B.html">Ch1-2 初始化流程</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-3%20%E5%88%9B%E5%BB%BAVK%E5%AE%9E%E4%BE%8B%E4%B8%8E%E9%80%BB%E8%BE%91%E8%AE%BE%E5%A4%87.html">Ch1-3 创建VK实例与逻辑设备</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-4%20%E5%88%9B%E5%BB%BA%E4%BA%A4%E6%8D%A2%E9%93%BE.html">Ch1-4 创建交换链</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第二章 绘制一个三角形</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch2-0%20%E4%BB%A3%E7%A0%81%E6%95%B4%E7%90%86%E5%8F%8A%E4%B8%80%E4%BA%9B%E8%BE%85%E5%8A%A9%E7%B1%BB.html">Ch2-0 代码整理及一些辅助类</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-1%20Rendering%20Loop.html">Ch2-1 Rendering Loop</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-2%20%E5%88%9B%E5%BB%BA%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch2-2 创建渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-3%20%E5%88%9B%E5%BB%BA%E7%AE%A1%E7%BA%BF%E5%B9%B6%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2.html">Ch2-3 创建管线并绘制三角形</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第三章 纵观Vulkan</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html">Ch3-1 同步原语</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-2 图像与缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-3%20%E7%AE%A1%E7%BA%BF%E5%B8%83%E5%B1%80%E5%92%8C%E7%AE%A1%E7%BA%BF.html">Ch3-3 管线布局和管线</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-4%20%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch3-4 渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-5%20%E5%91%BD%E4%BB%A4%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-5 命令缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-6%20%E6%8F%8F%E8%BF%B0%E7%AC%A6.html">Ch3-6 描述符</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-7%20%E9%87%87%E6%A0%B7%E5%99%A8.html">Ch3-7 采样器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-8%20%E6%9F%A5%E8%AF%A2.html">Ch3-8 查询</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第四章 着色器</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch4-1%20%E7%9D%80%E8%89%B2%E5%99%A8%E6%A8%A1%E7%BB%84.html">Ch4-1 着色器模组</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-2%20%E9%A1%B6%E7%82%B9%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-2 顶点着色器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-3%20%E7%89%87%E6%AE%B5%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-3 片段着色器</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第五章 封装常用对象</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch5-0%20VKBase%2B.h.html">Ch5-0 VKBase+.h</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-1%20%E5%90%84%E7%A7%8D%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch5-1 各种缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-2%202D%E8%B4%B4%E5%9B%BE%E5%8F%8A%E7%94%9F%E6%88%90Mipmap.html">Ch5-2 2D贴图及生成Mipmap</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-3%202D%E8%B4%B4%E5%9B%BE%E6%95%B0%E7%BB%84.html">Ch5-3 2D贴图数组</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第六章 进阶Vulkan</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch6-0%20%E4%BD%BF%E7%94%A8%E6%96%B0%E7%89%88%E6%9C%AC%E7%89%B9%E6%80%A7.html">Ch6-0 使用新版本特性</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-1%20%E6%97%A0%E5%9B%BE%E5%83%8F%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch6-1 无图像帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-2%20%E5%8A%A8%E6%80%81%E6%B8%B2%E6%9F%93.html">Ch6-2 动态渲染</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第七章 基础示例</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch7-1%20%E5%88%9D%E8%AF%86%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-1 初识顶点缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-2%20%E5%88%9D%E8%AF%86%E7%B4%A2%E5%BC%95%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-2 初识索引缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-3%20%E5%88%9D%E8%AF%86%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%BB%98%E5%88%B6.html">Ch7-3 初识实例化绘制</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-4%20%E5%88%9D%E8%AF%86Push%20Constant.html">Ch7-4 初识Push Constant</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-5%20%E5%88%9D%E8%AF%86Uniform%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-5 初识Uniform缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-6%20%E6%8B%B7%E8%B4%9D%E5%9B%BE%E5%83%8F%E5%88%B0%E5%B1%8F%E5%B9%95.html">Ch7-6 拷贝图像到屏幕</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html">Ch7-7 使用贴图</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第八章 简单示例</span></p>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">Ch8-1 离屏渲染</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#id1">离屏渲染的流程</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id2">图像附件</a></li>
<li class="toctree-l2"><a class="reference internal" href="#createrpwf-canvas">CreateRpwf_Canvas</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id3">来画线吧！</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#id4">画线的着色器</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id5">采样画布的着色器</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#id6">创建管线</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id7">清空画布</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id8">录制渲染命令</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id9">写这节时遭遇的硬件差异问题</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-2%20%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%95%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%8F%AF%E8%A7%86%E5%8C%96.html">Ch8-2 深度测试和深度可视化</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-3%20%E5%BB%B6%E8%BF%9F%E6%B8%B2%E6%9F%93.html">Ch8-3 延迟渲染</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-4%20%E9%A2%84%E4%B9%98Alpha.html">Ch8-4 预乘Alpha</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-5%20sRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%BC%80%E5%90%AFHDR.html">Ch8-5 sRGB色彩空间与开启HDR</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">附录</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ap1-1%20%E8%BF%90%E8%A1%8C%E6%9C%9F%E7%BC%96%E8%AF%91GLSL.html">Ap1-1 运行期编译GLSL</a></li>
</ul>

        </div>
      </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
          <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
          <a href="index.html">EasyVulkan</a>
      </nav>

      <div class="wy-nav-content">
        <div class="rst-content">
          <div role="navigation" aria-label="Page navigation">
  <ul class="wy-breadcrumbs">
      <li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
      <li>Ch8-1 离屏渲染</li>
      <li class="wy-breadcrumbs-aside">
      </li>
  </ul>
  <hr/>
</div>
          <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
           <div itemprop="articleBody">
             
  <section id="ch8-1">
<h1>Ch8-1 离屏渲染<a class="headerlink" href="#ch8-1" title="Permalink to this heading"></a></h1>
<p>
    本节的<span class="path">main.cpp</span>对应示例代码中的：<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/tree/main/solution/EasyVulkan_Ch8/Ch8-1.hpp">Ch8-1.hpp</a>
</p>
<p>
    我们先前始终是将内容直接渲染到屏幕，即交换链图像的。
    <br>
    离屏渲染（offscreen rendering）指将图片渲染到交换链图像之外的图像上，意义在于：
    <br>
    1.可以之后通过采样（需要后处理或旋转图像等情况）或blit（只是显示画中画的情况）进行一定的处理之后再呈现到交换链图像
    <br>
    2.可以将渲染结果用作程序运行过程中长期使用的贴图
    <br>
    3.程序可能没有交换链（比如只用来生成图像并保存到文件的控制台程序）
    <br>
    4.需要绘制的内容被保留到后续（当前帧交换链图像的内容当然也可以保留到下一帧，但是因为交换链图像有复数张，处理起来比较麻烦）
</p><section id="id1">
<h2>离屏渲染的流程<a class="headerlink" href="#id1" title="Permalink to this heading"></a></h2>
<p>
    在你已经写过了Ch7代码的基础上，进行离屏渲染并将结果采样到屏幕的步骤如下：
    <br>
    1.创建图像附件
    <br>
    2.创建离屏渲染的渲染通道和帧缓冲
    <br>
    3.为离屏渲染书写着色器并创建管线（可以接着沿用画三角形的，不过干脆换个别的吧！）
    <br>
    4.为屏幕渲染书写着色器并创建管线（可以接着沿用...但换个别的！）
    <br>
    5.分配描述符集（描述符布局在上一步中创建），将图像附件的view写入描述符
    <br>
    6.渲染到离屏帧缓冲
    <br>
    7.采样贴图，渲染到交换链图像
</p>
<p>
    我打算让上述的最后两步“离屏渲染”和“渲染到屏幕”发生在一个命令缓冲区中，中间不使用信号量或栅栏等同步对象。
    <br>
    “离屏渲染”和“渲染到屏幕”的两个渲染通道可以变成同一个渲染通道的两个子通道，不过这一节姑且就不这么做了。
</p></section>
<section id="id2">
<h2>图像附件<a class="headerlink" href="#id2" title="Permalink to this heading"></a></h2>
<p>
    先前我们都是使用交换链图像的image view作为图像附件。从头创建图像附件，必然要先创建图像，然后再为其创建view。
    <br>
    在<span class="path">VKBase+.h</span>中，vulkan命名空间中定义类<span class="type">attachment</span>：
</p>
<pre class="code">
<span class="kw">class</span> <span class="type">attachment</span> {
<span class="kw">protected</span>:
    <span class="type">imageView</span> imageView;
    <span class="type">imageMemory</span> imageMemory;
    <span class="cmt">//--------------------</span>
    <span class="fn">attachment</span>() = <span class="kw">default</span>;
<span class="kw">public</span>:
    <span class="cmt">//Getter</span>
    <span class="type">VkImageView</span> <span class="fn">ImageView</span>() <span class="kw">const</span> { <span class="kw">return</span> imageView; }
    <span class="type">VkImage</span> <span class="fn">Image</span>() <span class="kw">const</span> { <span class="kw">return</span> imageMemory.<span class="fn">Image</span>(); }
    <span class="kw">const</span> <span class="type">VkImageView</span>* <span class="fn">AddressOfImageView</span>() <span class="kw">const</span> { <span class="kw">return</span> imageView.<span class="fn">Address</span>(); }
    <span class="kw">const</span> <span class="type">VkImage</span>* <span class="fn">AddressOfImage</span>() <span class="kw">const</span> { <span class="kw">return</span> imageMemory.<span class="fn">AddressOfImage</span>(); }
    <span class="cmt">//Const Function</span>
    <span class="cmt">//该函数返回写入描述符时需要的信息</span>
    <span class="type">VkDescriptorImageInfo</span> <span class="fn">DescriptorImageInfo</span>(<span class="type">VkSampler</span> sampler) <span class="kw">const</span> {
        <span class="kw">return</span> { sampler, imageView, <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span> };
    }
};
</pre>
<p>
    然后派生出两个类：颜色附件<span class="type">colorAttachment</span>、深度模板附件<span class="type">depthStencilAttachment</span>（这一节还用不着深度模板附件，算是为之后做准备）。
    <br>
    输入附件不需要封装，输入附件是被后续子通道读取的图像附件，在创建颜色/深度模板附件时注明<span class="enum">VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT</span>即可。至于解析附件只不过是采样点数为1的颜色/深度模板附件罢了。
</p>
<pre class="code">
<span class="kw">class</span> <span class="type">colorAttachment</span> :<span class="kw">public</span> <span class="type">attachment</span> {
<span class="kw">public</span>:
    <span class="fn">colorAttachment</span>() = <span class="kw">default</span>;
    <span class="fn">colorAttachment</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
        <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0) {
        <span class="fn">Create</span>(format, extent, layerCount, sampleCount, otherUsages);
    }
    <span class="cmt">//Non-const Function</span>
    <span class="kw">void</span> <span class="fn">Create</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
        <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="cmt">//Static Function</span>
    <span class="cmt">//该函数用于检查某一格式的图像可否被用作颜色附件</span>
    <span class="kw">static bool</span> <span class="sfn">FormatAvailability</span>(<span class="type">VkFormat</span> format, <span class="kw">bool</span> supportBlending = true) {
        <span class="cmt">/*待填充*/</span>
    }
};
<span class="kw">class</span> <span class="type">depthStencilAttachment</span> :<span class="kw">public</span> <span class="type">attachment</span> {
<span class="kw">public</span>:
    <span class="enum">depthStencilAttachment</span>() = <span class="kw">default</span>;
    <span class="enum">depthStencilAttachment</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
        <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0, <span class="kw">bool</span> stencilOnly = <span class="kw">false</span>) {
        <span class="fn">Create</span>(format, extent, layerCount, sampleCount, otherUsages, stencilOnly);
    }
    <span class="cmt">//Non-const Function</span>
    <span class="kw">void</span> <span class="fn">Create</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
        <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0, <span class="kw">bool</span> stencilOnly = <span class="kw">false</span>) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="cmt">//Static Function</span>
    <span class="cmt">//该函数用于检查某一格式的图像可否被用作深度模板附件</span>
    <span class="kw">static bool</span> <span class="sfn">FormatAvailability</span>(<span class="type">VkFormat</span> format) {
        <span class="cmt">/*待填充*/</span>
    }
};
</pre>
<p>
    于是先来填充两个<span class="sfn">FormatAvailability</span>(...)。<a href="https://renderdoc.org/vkspec_chunked/chap48.html#VkFormatProperties">VkFormatProperties</a>的成员optimalTilingFeatures和linearTilingFeatures分别代表图像数据的排列方式（<span class="type">VkImageCreateInfo</span>::tiling）为最优排列和线性排列时，某种格式的图像具有的特性。
    <br>
    出于以下理由，不关心线性排列时的格式特性：
    <br>
    1.独显对线性数据排列的渲染目标支持较差，可能完全不支持。
    <br>
    2.总是倾向于使用最优数据排列以获得最佳性能。
</p>
<p>
    先前在<a class="reference internal" href="Ch5-0%20VKBase%2B.h.html">Ch5-0 VKBase+.h</a>中获取了所有格式的<span class="type">VkFormatProperties</span>，现在用先前准备好的函数<span class="fn">FormatProperties</span>(...)获取格式属性，让其optimalTilingFeatures成员与相应的<span class="type">VkImageUsageFlagBits</span>枚举项做位与，即可知某种格式的图像能否用作颜色/深度模板附件：
</p>
<pre class="code">
<span class="kw">static</span> <span class="type">colorAttachment</span>::<span class="sfn">FormatAvailability</span>(<spanclass="type">VkFormat</span> format, <span class="kw">bool</span> supportBlending = true) {
    <span class="kw">return</span> <span class="fn">FormatProperties</span>(format).optimalTilingFeatures &amp; <span class="enum">VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT</span> &lt;&lt; <span class="type">uint32_t</span>(supportBlending);
}
<span class="kw">bool</span> <span class="type">depthStencilAttachment</span>::<span class="sfn">FormatAvailability</span>(<spanclass="type">VkFormat</span> format) {
    <span class="kw">return</span> <span class="fn">FormatProperties</span>(format).optimalTilingFeatures &amp; <span class="enum">VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT</span>;
}
</pre>
<ul>
    <li>
        <p>
            <span class="enum">VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT</span>左移1位得到<span class="enum">VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT</span>，表示支持作为图像附件且支持混色。
        </p>
    </li>
</ul>
<p>
    然后填充<span class="type">colorAttachment</span>::<span class="fn">Create</span>(...)：
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">colorAttachment</span>::<span class="fn">Create</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
    <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0) {
    <span class="type">VkImageCreateInfo</span> imageCreateInfo = {
        .imageType = <span class="enum">VK_IMAGE_TYPE_2D</span>,
        .format = format,
        .extent = { extent.width, extent.height, 1 },
        .mipLevels = 1,
        .arrayLayers = layerCount,
        .samples = sampleCount,
        .usage = <span class="enum">VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT</span> | otherUsages
    };
    imageMemory.<span class="fn">Create</span>(
        imageCreateInfo,
        <span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span> | <span class="kw">bool</span>(otherUsages &amp; <span class="enum">VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT</span>) * <span class="enum">VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT</span>);
    imageView.<span class="fn">Create</span>(
        imageMemory.<span class="fn">Image</span>(),
        layerCount > 1 ? <span class="enum">VK_IMAGE_VIEW_TYPE_2D_ARRAY</span> : <span class="enum">VK_IMAGE_VIEW_TYPE_2D</span>,
        format,
        { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, layerCount });
}
</pre>
<ul>
    <li>
        <p>
            内存属性当然得有<span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span>以获得最佳性能，除此之外根据<a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html#id18">惰性分配</a>的规定，如果图像用途包含<span class="enum">VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT</span>，内存属性必须具有<span class="enum">VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT</span>。
        </p>
    </li>
    <li>
        <p>
            图像视图的类型根据图层数进行了分支，但如果你之后打算把渲染好的图像作为单层2D贴图数组（而非普通2D贴图）采样，在图层数为1时将类型指定为<span class="enum">VK_IMAGE_VIEW_TYPE_2D_ARRAY</span>也没什么问题（前提是设备得支持多层帧缓冲）。
        </p>
    </li>
</ul>
<p>
    相比之下，<span class="type">depthStencilAttachment</span>::<span class="fn">Create</span>(...)多出一段根据图像格式确定<span class="type">VkImageSubresourceRange</span>::aspectMask的逻辑：
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">depthStencilAttachment</span>::<span class="fn">Create</span>(<span class="type">VkFormat</span> format, <span class="type">VkExtent2D</span> extent, <span class="type">uint32_t</span> layerCount = 1,
    <span class="type">VkSampleCountFlagBits</span> sampleCount = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>, <span class="type">VkImageUsageFlags</span> otherUsages = 0, <span class="kw">bool</span> stencilOnly = <span class="kw">false</span>) {
    <span class="type">VkImageCreateInfo</span> imageCreateInfo = {
        .imageType = <span class="enum">VK_IMAGE_TYPE_2D</span>,
        .format = format,
        .extent = { extent.width, extent.height, 1 },
        .mipLevels = 1,
        .arrayLayers = layerCount,
        .samples = sampleCount,
        .usage = <span class="enum">VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT</span> | otherUsages
    };
    imageMemory.<span class="fn">Create</span>(
        imageCreateInfo,
        <span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span> | <span class="kw">bool</span>(otherUsages &amp; <span class="enum">VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT</span>) * <span class="enum">VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT</span>);
    <span class="cmt">//确定aspcet mask-------------------------</span>
    <span class="type">VkImageAspectFlags</span> aspectMask = (!stencilOnly) * <span class="enum">VK_IMAGE_ASPECT_DEPTH_BIT</span>;
    <span class="kw">if</span> (format &gt; <span class="enum">VK_FORMAT_S8_UINT</span>)
        aspectMask |= <span class="enum">VK_IMAGE_ASPECT_STENCIL_BIT</span>;
    <span class="kw">else if</span> (format == <span class="enum">VK_FORMAT_S8_UINT</span>)
        aspectMask = <span class="enum">VK_IMAGE_ASPECT_STENCIL_BIT</span>;
    <span class="cmt">//----------------------------------------</span>
    imageView.<span class="fn">Create</span>(
        imageMemory.<span class="fn">Image</span>(),
        layerCount > 1 ? <span class="enum">VK_IMAGE_VIEW_TYPE_2D_ARRAY</span> : <span class="enum">VK_IMAGE_VIEW_TYPE_2D</span>,
        format,
        { aspectMask, 0, 1, 0, layerCount });
}
</pre>
<ul>
    <li>
        <p>
            这里<span class ="kw">if</span>-<span class="kw">else</span>分支的逻辑是：在仅有的几种<a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html#id17">深度模板格式</a>当中，只有<span class="enum">VK_FORMAT_S8_UINT</span>是只存储模板值的，而大于<span class="enum">VK_FORMAT_S8_UINT</span>的深度模板格式同时具有深度和模板值。
        </p>
    </li>
    <li>
        <p>
            格式中同时包含深度和模板分量，之后只使用深度测试/模板测试之一的话，aspectMask中可同时包含两者，但只指定其中之一也是没问题的。
            <br>
            但是用作输入附件或贴图时，必须只指定<span class="enum">VK_IMAGE_ASPECT_DEPTH_BIT</span>或<span class="enum">VK_IMAGE_ASPECT_STENCIL_BIT</span>。
        </p>
    </li>
</ul>
<p>
    <span class="var">stencilOnly</span>参数的解释：
    <br>
    你只打算使用深度测试的话，使用只有深度的格式即可，Vulkan标准规定实现必须支持<span class="enum">VK_FORMAT_D16_UNORM</span>格式的深度模板附件，然后<span class="enum">VK_FORMAT_X8_D24_UNORM_PACK32</span>（X8是填充字节）和<span class="enum">VK_FORMAT_D32_SFLOAT</span>之中至少有一个受支持。
    <br>
    但是，只打算使用模板测试时，实现不一定支持<span class="enum">VK_FORMAT_S8_UINT</span>格式的深度模板附件，可能不得不使用带深度值的格式，这里<span class="var">stencilOnly</span>意在让格式中带深度分量时使aspectMask中不标明深度（这样一来，要将图像用于采样时，就不必再另外创建image view）。
</p></section>
<section id="createrpwf-canvas">
<h2>CreateRpwf_Canvas<a class="headerlink" href="#createrpwf-canvas" title="Permalink to this heading"></a></h2>
<p>
    于是，向<span class="path">EasyVulkan.hpp</span>，easyVulkan命名空间中，定义<span class="type">renderPassWithFramebuffer</span>结构体，及用来创建图像附件、渲染通道、帧缓冲的函数<span class="fn">CreateRpwf_Canvas</span>(...)：
</p>
<pre class="code">
<span class="kw">namespace</span> easyVulkan {
    <span class="kw">struct</span> <span class="type">renderPassWithFramebuffer</span> {
        <span class="type">renderPass</span> renderPass;
        <span class="type">framebuffer</span> framebuffer;
    };
    <span class="kw">const auto</span>&amp; <span class="fn">CreateRpwf_Canvas</span>(<span class="type">VkExtent2D</span> canvasSize) {
        <span class="kw">static</span> <span class="type">renderPassWithFramebuffer</span> rpwf;

        <span class="cmt">/*待后续填充*/</span>

        <span class="kw">return</span> rpwf;
    }
}
</pre>
<p>
    所谓canvas，顾名思义，我打算将这次创建的图像附件用作画布，实现一个超简陋的画画功能，画布大小从<span class="fn">CreateRpwf_Canvas</span>(...)的参数传入。
</p>
<p>
    在easyVulkan命名空间中定义一个<span class="type">colorAttachment</span>对象，然后在<span class="fn">CreateRpwf_Canvas</span>(...)中创建图像附件，格式就跟交换链一样吧：
</p>
<pre class="code">
<span class="kw">namespace</span> easyVulkan {
    <span class="kw">struct</span> <span class="type">renderPassWithFramebuffer</span> {
        <span class="type">renderPass</span> renderPass;
        <span class="type">framebuffer</span> framebuffer;
    };

    <span class="type">colorAttachment</span> ca_canvas;

    <span class="kw">const auto</span>&amp; <span class="fn">CreateRpwf_Canvas</span>(<span class="type">VkExtent2D</span> canvasSize) {
        <span class="kw">static</span> <span class="type">renderPassWithFramebuffer</span> rpwf;

        ca_canvas.<span class="fn">Create</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat, canvasSize, 1, <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
            <span class="enum">VK_IMAGE_USAGE_SAMPLED_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>);

        <span class="cmt">/*待后续填充*/</span>

        <span class="kw">return</span> rpwf;
    }
}
</pre>
<ul>
    <li>
        <p>
            将离屏渲染的图像附件用作画布的话，渲染通道开始时当然不会清屏，那么图像用途中应包含<span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>，以使用<a href="https://renderdoc.org/vkspec_chunked/chap19.html#vkCmdClearColorImage">vkCmdClearColorImage</a>(...)清屏。
        </p>
    </li>
</ul>
<p>
    来创建渲染通道，先填写附件描述。
    <br>
    因为是画布，每次读取时不清屏，且要保留先前的内容，所以loadOp为<span class="enum">VK_ATTACHMENT_LOAD_OP_LOAD</span>，storeOp为<span class="enum">VK_ATTACHMENT_STORE_OP_STORE</span>。
    <br>
    这次的应用场景中，图像附件是“渲染→被采样→渲染→被采样”如此循环，所以前后内存布局都用<span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>即可。
</p>
<pre class="code">
<span class="kw">const auto</span>&amp; <span class="fn">CreateRpwf_Canvas</span>(<span class="type">VkExtent2D</span> canvasSize) {
    <span class="kw">static</span> <span class="type">renderPassWithFramebuffer</span> rpwf;

    ca_canvas.<span class="fn">Create</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat, canvasSize, 1, <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
        <span class="enum">VK_IMAGE_USAGE_SAMPLED_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>);

    <span class="type">VkAttachmentDescription</span> attachmentDescription = {
        .format = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat,
        .samples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
        .loadOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_LOAD</span>,
        .storeOp = <span class="enum">VK_ATTACHMENT_STORE_OP_STORE</span>,
        .stencilLoadOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_DONT_CARE</span>,
        .stencilStoreOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_DONT_CARE</span>,
        .initialLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>,
        .finalLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>
    };
    <span class="cmt">/*待后续填充*/</span>

    <span class="kw">return</span> rpwf;
}
</pre>
<p>
    毫无难度地填写掉子通道描述，并把渲染通道创建信息先填了：
</p>
<pre class="code">
<span class="type">VkAttachmentReference</span> attachmentReference = { 0, <span class="enum">VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL</span> };
<span class="type">VkSubpassDescription</span> subpassDescription = {
    .pipelineBindPoint = <span class="enum">VK_PIPELINE_BIND_POINT_GRAPHICS</span>,
    .colorAttachmentCount = 1,
    .pColorAttachments = &amp;attachmentReference
};
<span class="type">VkRenderPassCreateInfo</span> renderPassCreateInfo = {
    .attachmentCount = 1,
    .pAttachments = &amp;attachmentDescription,
    .subpassCount = 1,
    .pSubpasses = &amp;subpassDescription,
    .dependencyCount = 2,
    .pDependencies = subpassDependencies,
};
</pre>
<p>
    子通道依赖是子通道开始和结束时各一个，先看代码：
</p>
<pre class="code">
<span class="type">VkSubpassDependency</span> subpassDependencies[2] = {
    {
        .srcSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
        .dstSubpass = 0,
        .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
        .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
        .srcAccessMask = 0,
        .dstAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
        .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> },
    {
        .srcSubpass = 0,
        .dstSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
        .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
        .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
        .srcAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
        .dstAccessMask = <span class="enum">VK_ACCESS_SHADER_READ_BIT</span>,
        .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> }
};
</pre>
<p>
    先来解说结束时的情况，结束时的子通道依赖可以说是每个参数都有恰如其分的作用。
    <br>
    通道结束时的dstSubpass为<span class="mcr">VK_SUBPASS_EXTERNAL</span>，这里实质上指代之后“渲染到屏幕”。
    <br>
    srcStageMask和srcAccessMask应该不需要解释，采样属于<span class="enum">VK_ACCESS_SHADER_READ_BIT</span>，发生在片段着色器阶段所以dstStageMask是<span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>。
</p>
<p>
    渲染通道开始时的情况则与结束时相反即可。
    <br>
    srcAccessMask为0，指定<span class="enum">VK_ACCESS_SHADER_READ_BIT</span>这种读操作无意义，而前一帧中对该图像附件的写入结果在当前帧的<a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html#id10">可见性</a>，由在前一帧中等待<span class="var">semaphore_renderingIsOver</span>来确保。
    <br>
    指定srcStageMask为<span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>是多余的，因为有栅栏来同步，填写<span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>即可（你问我为什么每次都不这么写？为了特地反复强调这件事！）。
</p>
<pre class="code">
<span class="type">VkSubpassDependency</span> subpassDependencies[2] = {
    {
        .srcSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
        .dstSubpass = 0,
        .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
        .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
        .srcAccessMask = 0,
        .dstAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
        .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> },
    {
        .srcSubpass = 0,
        .dstSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
        .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
        .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
        .srcAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
        .dstAccessMask = <span class="enum">VK_ACCESS_SHADER_READ_BIT</span>,
        .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> }
};
</pre>
<p>
    最后非常简单无脑地创建帧缓冲，整个<span class="fn">CreateRpwf_Canvas</span>(...)如下：
</p>
<pre class="code">
<span class="kw">const auto</span>&amp; <span class="fn">CreateRpwf_Canvas</span>(<span class="type">VkExtent2D</span> canvasSize = windowSize) {
    <span class="kw">static</span> <span class="type">renderPassWithFramebuffer</span> rpwf;

    ca_canvas.<span class="fn">Create</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat, canvasSize, 1, <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
        <span class="enum">VK_IMAGE_USAGE_SAMPLED_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>);

    <span class="type">VkAttachmentDescription</span> attachmentDescription = {
        .format = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat,
        .samples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
        .loadOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_LOAD</span>,
        .storeOp = <span class="enum">VK_ATTACHMENT_STORE_OP_STORE</span>,
        .stencilLoadOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_DONT_CARE</span>,
        .stencilStoreOp = <span class="enum">VK_ATTACHMENT_LOAD_OP_DONT_CARE</span>,
        .initialLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>,
        .finalLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>
    };
    <span class="type">VkSubpassDependency</span> subpassDependencies[2] = {
        {
            .srcSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
            .dstSubpass = 0,
            .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
            .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
            .srcAccessMask = 0,
            .dstAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
            .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> },
        {
            .srcSubpass = 0,
            .dstSubpass = <span class="mcr">VK_SUBPASS_EXTERNAL</span>,
            .srcStageMask = <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>,
            .dstStageMask = <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>,
            .srcAccessMask = <span class="enum">VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT</span>,
            .dstAccessMask = <span class="enum">VK_ACCESS_SHADER_READ_BIT</span>,
            .dependencyFlags = <span class="enum">VK_DEPENDENCY_BY_REGION_BIT</span> }
    };
    <span class="type">VkAttachmentReference</span> attachmentReference = { 0, <span class="enum">VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL</span> };
    <span class="type">VkSubpassDescription</span> subpassDescription = {
        .pipelineBindPoint = <span class="enum">VK_PIPELINE_BIND_POINT_GRAPHICS</span>,
        .colorAttachmentCount = 1,
        .pColorAttachments = &amp;attachmentReference
    };
    <span class="type">VkRenderPassCreateInfo</span> renderPassCreateInfo = {
        .attachmentCount = 1,
        .pAttachments = &amp;attachmentDescription,
        .subpassCount = 1,
        .pSubpasses = &amp;subpassDescription,
        .dependencyCount = 2,
        .pDependencies = subpassDependencies,
    };

    <span class="type">VkFramebufferCreateInfo</span> framebufferCreateInfo = {
        .renderPass = rpwf.renderPass,
        .attachmentCount = 1,
        .pAttachments = ca_canvas.<span class="fn">AddressOfImageView</span>(),
        .width = canvasSize.width,
        .height = canvasSize.height,
        .layers = 1
    };
    rpwf.framebuffer.<span class="fn">Create</span>(framebufferCreateInfo);

    <span class="kw">return</span> rpwf;
}
</pre></section>
<section id="id3">
<h2>来画线吧！<a class="headerlink" href="#id3" title="Permalink to this heading"></a></h2>
<p>
    我打算实现一个非常简单的绘画功能：追纵鼠标指针的位置，将鼠标指针在当前帧和上一帧的位置用红线连起来。
    <br>
    然后在“渲染到屏幕时”使用两两间隔相同的三个uv坐标，采用三次，使其显示为红绿蓝三色的线，并让颜色叠加在一起，效果如下：
</p>
<img alt="_images/ch8-1-1.png" src="_images/ch8-1-1.png">
<p>
    （我知道不怎么有趣，不过我不想写一些数学上解释起来很麻烦的）
</p>
<p>
    为简化叙事，这节会全部使用push constant来向着色器提供数据，免去顶点/uniform等缓冲区。
</p><section id="id4">
<h3>画线的着色器<a class="headerlink" href="#id4" title="Permalink to this heading"></a></h3>
<p>
    用来画线的顶点着色器<span class="path">Line.vert.shader</span>：
</p>
<pre class="code">
<span class="pragma">#version</span> 460
<span class="pragma">#pragma shader_stage</span>(vertex)

<span class="kw">layout</span>(push_constant) <span class="kw">uniform</span> pushConstants {
    <span class="type">vec2</span> viewportSize;
    <span class="type">vec2</span> offsets[2];
};

<span class="kw">void</span> <span class="fn">main</span>() {
    gl_Position = <span class="type">vec4</span>(2 * offsets[gl_VertexIndex] / viewportSize - 1, 0, 1);
}
</pre>
<ul>
    这里<span class="var">viewportSize</span>是视口大小，将是等于画布大小。
    <br>
    <span class="var">offsets</span>是两个点相对于视口左上角的位置。
</p>
<p>
    视口对应整个NDC坐标[-1, 1]的区间，对于屏幕上任意点的像素单位坐标pos，到其在视口中NDC坐标范围[-1, 1]的转换为<code>(pos - viewportSize的一半) / viewportSize的一半</code>， 即<code>2 * pos / viewportSize - 1</code>
</p>
<p>
    片段着色器<span class="path">Line.frag.shader</span>（真是省事啊！）：
</p>
<pre class="code">
<span class="pragma">#version</span> 460
<span class="pragma">#pragma shader_stage</span>(fragment)

<span class="kw">layout</span>(location = 0) <span class="kw">out</span> <span class="type">vec4</span> o_Color;

<span class="kw">void</span> <span class="fn">main</span>() {
    o_Color = <span class="type">vec4</span>(1, 0, 0, 1);
}
</pre>
<p>
    之后创建管线时的<span class="type">VkPushConstantRange</span>：
</p>
<pre class="code">
<span class="type">VkPushConstantRange</span> pushConstantRange = { <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 24 };
</pre></section>
<section id="id5">
<h3>采样画布的着色器<a class="headerlink" href="#id5" title="Permalink to this heading"></a></h3>
<p>
    <span class="path">CanvasToScreen.vert.shader</span>：
</p>
<pre class="code">
<span class="pragma">#version</span> 460
<span class="pragma">#pragma shader_stage</span>(vertex)

<span class="type">vec2</span> positions[4] = {
    { 0, 0 },
    { 0, 1 },
    { 1, 0 },
    { 1, 1 }
};

<span class="kw">layout</span>(location = 0) <span class="kw">out</span> <span class="type">vec2</span> o_TexCoord;

<span class="kw">layout</span>(push_constant) <span class="kw">uniform</span> pushConstants {
    <span class="type">vec2</span> viewportSize;
    <span class="type">vec2</span> canvasSize;
};

<span class="kw">void</span> <span class="fn">main</span>() {
    o_TexCoord = positions[gl_VertexIndex];
    gl_Position = <span class="type">vec4</span>(2 * positions[gl_VertexIndex] * canvasSize / viewportSize - 1, 0, 1);
}
</pre>
<p>
    <span class="var">positions</span>是着色器里的变量，我没有用<span class="kw">const</span>修饰它，所以它是可修改的，修改只到影响本次（处理当前顶点的）着色器调用中的数值。
    <br>
    这里<span class="var">viewportSize</span>是视口大小，之后代码里“渲染到屏幕”的视口一如既往是交换链图像大小。
    <br>
    <span class="var">canvasSize</span>是画布大小，数值跟前面<span class="path">Line.vert.shader</span>中的<span class="var">viewportSize</span>一致。
</p>
<p>
    这个着色器的效果是贴着屏幕左上角渲染整张画布，因此四个顶点以像素为单位的位置是<code>positions[gl_VertexIndex] * canvasSize</code>，而贴图坐标非0即1。
</p>
<p>
    <span class="path">CanvasToScreen.frag.shader</span>：
</p>
<pre class="code">
<span class="pragma">#version</span> 460
<span class="pragma">#pragma shader_stage</span>(fragment)

<span class="kw">layout</span>(location = 0) <span class="kw">in</span> <span class="type">vec2</span> i_TexCoord;
<span class="kw">layout</span>(location = 0) <span class="kw">out</span> <span class="type">vec4</span> o_Color;
<span class="kw">layout</span>(binding = 0) <span class="kw">uniform</span> <span class="type">sampler2D</span> u_Texture;

<span class="kw">layout</span>(push_constant) <span class="kw">uniform</span> pushConstants {
    <span class="kw">layout</span>(offset = 8)
    <span class="type">vec2</span> canvasSize;
};

<span class="kw">void</span> <span class="fn">main</span>() {
    o_Color = <span class="fn">texture</span>(u_Texture, i_TexCoord);
    o_Color.g = <span class="fn">texture</span>(u_Texture, i_TexCoord + 8 / canvasSize).r;
    o_Color.b = <span class="fn">texture</span>(u_Texture, i_TexCoord - 8 / canvasSize).r;
}
</pre>
<ul>
    <li>
        <p>
            因为整张贴图大小是<span class="var">canvasSize</span>，<code>i_TexCoord + 8 / canvasSize</code>即从<span class="var">i_TexCoord</span>纵向横向都偏离8个像素的采样位置。
        </p>
    </li>
    <li>
        <p>
            这是除了对语法进行说明的<a class="reference internal" href="Ch4-1%20%E7%9D%80%E8%89%B2%E5%99%A8%E6%A8%A1%E7%BB%84.html#push-constant">Ch4-1 着色器模组</a>外，本教程中第一次出现<code><span class="kw">layout</span>(offset = 距起始位置的字节数)</code>这个语法。
            <br>
            这个着色器不需要位于push constant开头8个字节的数据，如果你想把<span class="var">viewportSize</span>声明上去，那也没什么关系（注：下面的<span class="type">VkPushConstantRange</span>要相应变更）。
        </p>
    </li>
</ul>
<p>
    之后创建管线时的<span class="type">VkPushConstantRange</span>：
</p>
<pre class="code">
<span class="type">VkPushConstantRange</span> pushConstantRanges[2] = {
    { <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 16 },
    { <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>, 8, 8 }
};
</pre></section>
</section>
<section id="id6">
<h2>创建管线<a class="headerlink" href="#id6" title="Permalink to this heading"></a></h2>
<p>
    跟管线相关的对象一共5个：
</p>
<pre class="code">
<span class="pragma">#include</span> <span class="str">&quot;GlfwGeneral.hpp&quot;</span>
<span class="pragma">#include</span> <span class="str">&quot;EasyVulkan.hpp&quot;</span>
<span class="kw">using namespace</span> vulkan;

<span class="cmt">//离屏，画线</span>
<span class="type">pipelineLayout</span> pipelineLayout_line;
<span class="type">pipeline</span> pipeline_line;
<span class="cmt">//屏幕，采样贴图</span>
<span class="type">descriptorSetLayout</span> descriptorSetLayout_texture;
<span class="type">pipelineLayout</span> pipelineLayout_screen;
<span class="type">pipeline</span> pipeline_screen;

<span class="kw">const auto</span>&amp; <span class="fn">RenderPassAndFramebuffers_Screen</span>() {
    <span class="kw">static const auto</span>&amp; rpwf = easyVulkan::<span class="fn">CreateRpwf_Screen</span>();
    <span class="kw">return</span> rpwf;
}
<span class="kw">const auto</span>&amp; <span class="fn">RenderPassAndFramebuffer_Offscreen</span>(<span class="type">VkExtent2D</span> canvasSize) {
    <span class="kw">static const auto</span>&amp; rpwf = easyVulkan::<span class="fn">CreateRpwf_Canvas</span>(canvasSize);
    <span class="kw">return</span> rpwf;
}
<span class="kw">void</span> <span class="fn">CreateLayout</span>() {
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">void</span> <span class="fn">CreatePipeline</span>(<span class="type">VkExtent2D</span> canvasSize) {
    <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    先创建“离屏”的管线布局，只有push constant范围：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CreateLayout</span>() {
    <span class="type">VkPushConstantRange</span> pushConstantRange_offscreen = { <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 24 };
    <span class="type">VkPipelineLayoutCreateInfo</span> pipelineLayoutCreateInfo = {
        .pushConstantRangeCount = 1,
        .pPushConstantRanges = &amp;pushConstantRange_offscreen,
    };
    pipelineLayout_line.<span class="fn">Create</span>(pipelineLayoutCreateInfo);
    <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    创建<span class="var">descriptorSetLayout_texture</span>，同Ch7-7完全一样：
</p>
<pre class="code">
<span class="type">VkDescriptorSetLayoutBinding</span> descriptorSetLayoutBinding_texture = {
    .binding = 0,
    .descriptorType = <span class="enum">VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER</span>,
    .descriptorCount = 1,
    .stageFlags = <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>
};
<span class="type">VkDescriptorSetLayoutCreateInfo</span> descriptorSetLayoutCreateInfo_texture = {
    .bindingCount = 1,
    .pBindings = &amp;descriptorSetLayoutBinding_texturePosition
};
descriptorSetLayout_texture.<span class="fn">Create</span>(descriptorSetLayoutCreateInfo_texture);
</pre>
<p>
    剩下也没什么好说的，完事：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CreateLayout</span>() {
    <span class="cmt">//离屏</span>
    <span class="type">VkPushConstantRange</span> pushConstantRange_offscreen = { <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 24 };
    <span class="type">VkPipelineLayoutCreateInfo</span> pipelineLayoutCreateInfo = {
        .pushConstantRangeCount = 1,
        .pPushConstantRanges = &amp;pushConstantRange_offscreen,
    };
    pipelineLayout_line.<span class="fn">Create</span>(pipelineLayoutCreateInfo);
    <span class="cmt">//屏幕</span>
    <span class="type">VkDescriptorSetLayoutBinding</span> descriptorSetLayoutBinding_texture = { 0, <span class="enum">VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER</span>, 1, <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span> };
    <span class="type">VkDescriptorSetLayoutCreateInfo</span> descriptorSetLayoutCreateInfo_texture = {
        .bindingCount = 1,
        .pBindings = &amp;descriptorSetLayoutBinding_texturePosition
    };
    descriptorSetLayout_texture.<span class="fn">Create</span>(descriptorSetLayoutCreateInfo_texture);
    <span class="type">VkPushConstantRange</span> pushConstantRanges_screen[] = {
        { <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 16 },
        { <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>, 8, 8 }
    };
    pipelineLayoutCreateInfo.pushConstantRangeCount = 2;
    pipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges_screen;
    pipelineLayoutCreateInfo.setLayoutCount = 1;
    pipelineLayoutCreateInfo.pSetLayouts = descriptorSetLayout_texture.<span class="fn">Address</span>();
    pipelineLayout_screen.<span class="fn">Create</span>(pipelineLayoutCreateInfo);
}
</pre>
<p>
    创建画线的管线，代码不需要放进lambda，因为窗口大小改变时，画布大小不必跟着变。
    <br>
    相比先前画三角形时的不同在于绘制模式和指定了线宽：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CreateLayout</span>() {
    <span class="kw">static</span> <span class="type">shaderModule</span> vert_offscreen(<span class="str">"shader/Line.vert.spv"</span>);
    <span class="kw">static</span> <span class="type">shaderModule</span> frag_offscreen(<span class="str">"shader/Line.frag.spv"</span>);
    <span class="type">VkPipelineShaderStageCreateInfo</span> shaderStageCreateInfos_line[2] = {
        vert_offscreen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>),
        frag_offscreen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>)
    };
    <span class="type">graphicsPipelineCreateInfoPack</span> pipelineCiPack;
    pipelineCiPack.createInfo.layout = pipelineLayout_line;
    pipelineCiPack.createInfo.renderPass = <span class="fn">RenderPassAndFramebuffer_Offscreen</span>(canvasSize).renderPass;
    pipelineCiPack.inputAssemblyStateCi.topology = <span class="enum">VK_PRIMITIVE_TOPOLOGY_LINE_LIST</span>;<span class="cmt">//绘制线</span>
    pipelineCiPack.viewports.<span class="fn">emplace_back</span>(0.f, 0.f, <span class="kw">float</span>(canvasSize.width), <span class="kw">float</span>(canvasSize.height), 0.f, 1.f);
    pipelineCiPack.scissors.<span class="fn">emplace_back</span>(<span class="type">VkOffset2D</span>{}, canvasSize);
    pipelineCiPack.rasterizationStateCi.lineWidth = 1;                             <span class="cmt">//线宽</span>
    pipelineCiPack.multisampleStateCi.rasterizationSamples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>;
    pipelineCiPack.colorBlendAttachmentStates.<span class="fn">push_back</span>({ .colorWriteMask = 0b1111 });
    pipelineCiPack.<span class="fn">UpdateAllArrays</span>();
    pipelineCiPack.createInfo.stageCount = 2;
    pipelineCiPack.createInfo.pStages = shaderStageCreateInfos_line;
    pipeline_line.<span class="fn">Create</span>(pipelineCiPack);
    <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    渲染到屏幕的管线，跟Ch2中创建画三角形管线的代码之差别只有绘制模式（不算文件路径和变量名的不同的话）：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CreateLayout</span>() {
    <span class="cmt">//离屏</span>
    <span class="kw">static</span> <span class="type">shaderModule</span> vert_offscreen(<span class="str">"shader/Line.vert.spv"</span>);
    <span class="kw">static</span> <span class="type">shaderModule</span> frag_offscreen(<span class="str">"shader/Line.frag.spv"</span>);
    <span class="type">VkPipelineShaderStageCreateInfo</span> shaderStageCreateInfos_line[2] = {
        vert_offscreen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>),
        frag_offscreen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>)
    };
    <span class="type">graphicsPipelineCreateInfoPack</span> pipelineCiPack;
    pipelineCiPack.createInfo.layout = pipelineLayout_line;
    pipelineCiPack.createInfo.renderPass = <span class="fn">RenderPassAndFramebuffers_Screen</span>(canvasSize).renderPass;
    pipelineCiPack.inputAssemblyStateCi.topology = <span class="enum">VK_PRIMITIVE_TOPOLOGY_LINE_LIST</span>;<span class="cmt">//绘制线</span>
    pipelineCiPack.viewports.<span class="fn">emplace_back</span>(0.f, 0.f, <span class="kw">float</span>(canvasSize.width), <span class="kw">float</span>(canvasSize.height), 0.f, 1.f);
    pipelineCiPack.scissors.<span class="fn">emplace_back</span>(<span class="type">VkOffset2D</span>{}, canvasSize);
    pipelineCiPack.rasterizationStateCi.lineWidth = 1;                             <span class="cmt">//线宽</span>
    pipelineCiPack.multisampleStateCi.rasterizationSamples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>;
    pipelineCiPack.colorBlendAttachmentStates.<span class="fn">push_back</span>({ .colorWriteMask = 0b1111 });
    pipelineCiPack.<span class="fn">UpdateAllArrays</span>();
    pipelineCiPack.createInfo.stageCount = 2;
    pipelineCiPack.createInfo.pStages = shaderStageCreateInfos_line;
    pipeline_line.<span class="fn">Create</span>(pipelineCiPack);
    <span class="cmt">//屏幕</span>
    <span class="kw">static</span> <span class="type">shaderModule</span> vert_screen(<span class="str">&quot;shader/CanvasToScreen.vert.spv&quot;</span>);
    <span class="kw">static</span> <span class="type">shaderModule</span> frag_screen(<span class="str">&quot;shader/CanvasToScreen.frag.spv&quot;</span>);
    <span class="kw">static</span> <span class="type">VkPipelineShaderStageCreateInfo</span> shaderStageCreateInfos_screen[2] = {
        vert_screen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>),
        frag_screen.<span class="fn">StageCreateInfo</span>(<span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>)
    };
    <span class="kw">auto</span> Create = [] {
        <span class="type">graphicsPipelineCreateInfoPack</span> pipelineCiPack;
        pipelineCiPack.createInfo.layout = pipelineLayout_screen;
        pipelineCiPack.createInfo.renderPass = <span class="fn">RenderPassAndFramebuffers_Screen</span>().renderPass;
        pipelineCiPack.inputAssemblyStateCi.topology = <span class="enum">VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP</span>;<span class="cmt">//绘制模式跟Ch7-7一样</span>
        pipelineCiPack.viewports.<span class="fn">emplace_back</span>(0.f, 0.f, <span class="kw">float</span>(windowSize.width), <span class="kw">float</span>(windowSize.height), 0.f, 1.f);
        pipelineCiPack.scissors.<span class="fn">emplace_back</span>(<span class="type">VkOffset2D</span>{}, windowSize);
        pipelineCiPack.multisampleStateCi.rasterizationSamples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>;
        pipelineCiPack.colorBlendAttachmentStates.<span class="fn">push_back</span>({ .colorWriteMask = 0b1111 });
        pipelineCiPack.<span class="fn">UpdateAllArrays</span>();
        pipelineCiPack.createInfo.stageCount = 2;
        pipelineCiPack.createInfo.pStages = shaderStageCreateInfos_screen;
        pipeline_screen.<span class="fn">Create</span>(pipelineCiPack);
    };
    <span class="kw">auto</span> Destroy = [] {
        pipeline_screen.<span class="fn">~pipeline</span>();
    };
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">AddCallback_CreateSwapchain</span>(Create);
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">AddCallback_DestroySwapchain</span>(Destroy);
    Create();
}
</pre></section>
<section id="id7">
<h2>清空画布<a class="headerlink" href="#id7" title="Permalink to this heading"></a></h2>
<p>
    在书写主函数前还有一步，需要写一个清空画布的函数。
    <br>
    不光是为了实现这个功能本身，刚才创建渲染通道时，图像附件的初始图像布局为<span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>，可是刚创建好的图像附件的布局为<span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>，内容也可能是一堆内存垃圾。
</p>
<p>
    向<span class="path">EasyVulkan.hpp</span>，easyVulkan命名空间中，定义函数<span class="fn">CmdClearCanvas</span>(...)，用于清屏并转换图像布局：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CmdClearCanvas</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkClearColorValue</span> clearColor) {
    <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    要干的事情一共三件：清屏前的内存屏障、调用<span class="fn">vkCmdClearColorImage</span>(...)清屏、清屏后的内存屏障。
</p>
<p>
    <span class="fn">vkCmdClearColorImage</span>(...)属于数据转移命令，清屏前的内存屏障要将图像内存布局从<span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>转到<span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>待写：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CmdClearCanvas</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkClearColorValue</span> clearColor) {
    <span class="type">VkImageSubresourceRange</span> imageSubresourceRange = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 };
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        0,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        ca_canvas.<span class="fn">Image</span>(),
        imageSubresourceRange
    };
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);

    <span class="cmt">/*待填充*/</span>
}
</pre>
<ul>
    <li>
        <p>
            在渲染循环中第二次执行本函数时，图像的内存布局已经是<span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>了，但因为是清屏（不关心原有内容），清屏前的内存屏障中oldLayout永远都是<span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>即可。
        </p>
    </li>
</ul>
<p>
    书写清屏后的内存屏障。
    <br>
    先前创建渲染通道时，指定了渲染通道开始时的图像内存布局为<span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>，清屏后内存布局就转到这个，不要转到<span class="enum">VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL</span>。
    <br>
    因为之后有子通道依赖，dstStageMask就算填写成晚于<span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>的阶段也没关系，内存布局转换具有隐式同步保证，这个屏障引发的转换一定先于渲染通道开始时的依赖：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CmdClearCanvas</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkClearColorValue</span> clearColor) {
    <span class="type">VkImageSubresourceRange</span> imageSubresourceRange = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 };
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        0,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        ca_canvas.<span class="fn">Image</span>(),
        imageSubresourceRange
    };
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);

    <span class="cmt">/*调用命令清屏，待填充*/</span>

    imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
    imageMemoryBarrier.dstAccessMask = 0;
    imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
    imageMemoryBarrier.newLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>;
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
}
</pre>
<p>
    正题：
</p>
</pre>
    <table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">void</span> <span class="mcr">VKAPI_CALL</span> <a href="https://renderdoc.org/vkspec_chunked/chap19.html#vkCmdClearColorImage">vkCmdClearColorImage</a>(...) 的参数说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkCommandBuffer</span> commandBuffer</p></td>
            <td><p>命令缓冲区的handle</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkImage</span> image</p></td>
            <td><p>图像的handle</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkImageLayout</span> imageLayout</p></td>
            <td><p>图像的内存布局</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const</span> <span class="type">VkClearColorValue</span>* pColor</p></td>
            <td><p>指向用于清屏的填充色</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">uint32_t</span> rangeCount</p></td>
            <td><p>清空范围的个数</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const</span> <span class="type">VkImageSubresourceRange</span>* pRanges</td>
            <td><p>指向<span class="type">VkImageSubresourceRange</span>的数组，用于指定清空范围</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <p>
            该函数只清空颜色图像，若要清空深度模板图像，使用<a href="https://renderdoc.org/vkspec_chunked/chap19.html#vkCmdClearDepthStencilImage">vkCmdClearDepthStencilImage</a>(...)。
        </p>
    </li>
</ul>
<p>
    清空整张图，毫无难度地写完：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">CmdClearCanvas</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkClearColorValue</span> clearColor) {
    <span class="type">VkImageSubresourceRange</span> imageSubresourceRange = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 };
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        0,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        ca_canvas.<span class="fn">Image</span>(),
        imageSubresourceRange
    };
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);

    <span class="fn">vkCmdClearColorImage</span>(commandBuffer, ca_canvas.<span class="fn">Image</span>(), <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>, &amp;clearColor, 1, &amp;imageSubresourceRange);

    imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
    imageMemoryBarrier.dstAccessMask = 0;
    imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
    imageMemoryBarrier.newLayout = <span class="enum">VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL</span>;
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
}
</pre></section>
<section id="id8">
<h2>录制渲染命令<a class="headerlink" href="#id8" title="Permalink to this heading"></a></h2>
<p>
    主函数框架如下，都是你见过的东西。
    <br>
    在里面定义画布大小<span class="var">canvas</span>，将图像附件的view写入描述符：
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="kw">if</span> (!<span class="fn">InitializeWindow</span>({1280,720}))
        <span class="kw">return</span> -1;

    <span class="cmt">//画布大小</span>;
    <span class="type">VkExtent2D</span> canvasSize = windowSize;

    <span class="kw">const auto</span>&amp; [renderPass_screen, framebuffers_screen] = <span class="fn">RenderPassAndFramebuffers_Screen</span>();
    <span class="kw">const auto</span>&amp; [renderPass_offscreen, framebuffer_offscreen] = <span class="fn">RenderPassAndFramebuffer_Offscreen</span>();
    <span class="fn">CreateLayout</span>();
    <span class="fn">CreatePipeline</span>();

    <span class="type">fence</span> fence;
    <span class="type">semaphore</span> semaphore_imageIsAvailable;
    <span class="type">semaphore</span> semaphore_renderingIsOver;

    <span class="type">commandBuffer</span> commandBuffer;
    <span class="type">commandPool</span> commandPool(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">QueueFamilyIndex_Graphics</span>(), <span class="enum">VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT</span>);
    commandPool.<span class="type">AllocateBuffers</span>(commandBuffer);

    <span class="type">VkSamplerCreateInfo</span> samplerCreateInfo = <span class="type">texture</span>::<span class="sfn">SamplerCreateInfo</span>();
    <span class="type">sampler</span> sampler(samplerCreateInfo);
    <span class="type">VkDescriptorPoolSize</span> descriptorPoolSizes[] = {
        { <span class="enum">VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER</span>, 1 }
    };
    <span class="type">descriptorPool descriptorPool</span> descriptorPool(1, descriptorPoolSizes);
    <span class="type">descriptorSet</span> descriptorSet_texture;
    descriptorPool.<span class="fn">AllocateSets</span>(descriptorSet_texture, descriptorSetLayout_texture);
    <span class="cmt">//写入描述符</span>;
    descriptorSet_texture.<span class="fn">Write</span>(easyVulkan::ca_canvas.<span class="fn">DescriptorImageInfo</span>(sampler), <span class="enum">VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER</span>);

    <span class="cmt">/*待填充*/</span>

    <span class="kw">while</span> (!<span class="fn">glfwWindowShouldClose</span>(pWindow)) {
        <span class="kw">while</span> (<span class="fn">glfwGetWindowAttrib</span>(pWindow, <span class="mcr">GLFW_ICONIFIED</span>))
            <span class="fn">glfwWaitEvents</span>();

        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);
        <span class="kw">auto</span> i = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>();
        commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);

        <span class="cmt">/*待填充*/</span>

        commandBuffer.<span class="fn">End</span>();
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(commandBuffer, semaphore_imageIsAvailable, semaphore_renderingIsOver, fence);
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>(semaphore_renderingIsOver);

        <span class="fn">glfwPollEvents</span>();

        <span class="cmt">/*操作逻辑，待填充*/</span>

        <span class="fn">TitleFps</span>();

        fence.<span class="fn">WaitAndReset</span>();
    }
    <span class="fn">TerminateWindow</span>();
    <span class="kw">return</span> 0;
}
</pre>
<ul>
    <li>
        <p>
            因为每一帧中使用的是相同的离屏帧缓冲，不能应用<a class="reference internal" href="Ch2-1%20Rendering%20Loop.html#id8">即时帧</a>（即每一帧应使用同一套同步对象）。
        </p>
    </li>
</ul>
<p>
    这里操作逻辑被我放到了<span class="fn">glfwPollEvents</span>()之后，嘛在循环里的话其实放哪儿都差不多，不过这里是有点讲究的。
    <br>
    我们首先将画布清空为全透明，因为不会每次都清屏，所以用一个初值为<span class="kw">true</span>的布尔值来指示是否清空：
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="cmt">/*...前略*/</span>

    <span class="cmt">/*待填充*/</span>

    <span class="kw">bool</span> clearCanvas = <span class="kw">true</span>;

    <span class="kw">while</span> (!<span class="fn">glfwWindowShouldClose</span>(pWindow)) {
        <span class="kw">while</span> (<span class="fn">glfwGetWindowAttrib</span>(pWindow, <span class="mcr">GLFW_ICONIFIED</span>))
            <span class="fn">glfwWaitEvents</span>();

        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);
        <span class="kw">auto</span> i = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>();
        commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);

        <span class="kw">if</span> (clearCanvas)
            easyVulkan::<span class="fn">CmdClearCanvas</span>(commandBuffer, <span class="type">VkClearColorValue</span>{}),
            clearCanvas = <span class="kw">false</span>;

        <span class="cmt">/*待填充*/</span>

        commandBuffer.<span class="fn">End</span>();
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(commandBuffer, semaphore_imageIsAvailable, semaphore_renderingIsOver, fence);
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>(semaphore_renderingIsOver);

        <span class="fn">glfwPollEvents</span>();

        <span class="cmt">/*操作逻辑，待填充*/</span>

        <span class="fn">TitleFps</span>();

        fence.<span class="fn">WaitAndReset</span>();
    }
    <span class="fn">TerminateWindow</span>();
    <span class="kw">return</span> 0;
}
</pre>
<p>
    操作逻辑就简单点，按下左键就清空吧。
    <br>
    用<a href="https://www.glfw.org/docs/latest/group__input.html#gac1473feacb5996c01a7a5a33b5066704">glfwGetMouseButton</a>(...)获取鼠标按键状态，参数不言自明，到GLFW3.4为止只返回<span class="mcr">GLFW_PRESS</span>（按下，值为1）或<span class="mcr">GLFW_RELEASE</span>（没按下，值为0）两种：
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="cmt">/*...前略*/</span>

    <span class="cmt">/*待填充*/</span>

    <span class="kw">bool</span> clearCanvas = <span class="kw">true</span>;

    <span class="kw">while</span> (!<span class="fn">glfwWindowShouldClose</span>(pWindow)) {
        <span class="kw">while</span> (<span class="fn">glfwGetWindowAttrib</span>(pWindow, <span class="mcr">GLFW_ICONIFIED</span>))
            <span class="fn">glfwWaitEvents</span>();

        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);
        <span class="kw">auto</span> i = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>();
        commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);

        <span class="kw">if</span> (clearCanvas)
            easyVulkan::<span class="fn">CmdClearCanvas</span>(commandBuffer, <span class="type">VkClearColorValue</span>{}),
            clearCanvas = <span class="kw">false</span>;

        <span class="cmt">/*待填充*/</span>

        commandBuffer.<span class="fn">End</span>();
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(commandBuffer, semaphore_imageIsAvailable, semaphore_renderingIsOver, fence);
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>(semaphore_renderingIsOver);

        <span class="fn">glfwPollEvents</span>();

        <span class="cmt">/*待后续填充*/</span>
        clearCanvas = <span class="fn">glfwGetMouseButton</a>(pWindow, <span class="mcr">GLFW_MOUSE_BUTTON_LEFT</span>)

        <span class="fn">TitleFps</span>();

        fence.<span class="fn">WaitAndReset</span>();
    }
    <span class="fn">TerminateWindow</span>();
    <span class="kw">return</span> 0;
}
</pre>
<ul>
    <li>
        <p>
            所以操作逻辑放这个位置的原因也一目了然：防止<span class="var">clearCanvas</span>在首次清空画布前变成<span class="kw">false</span>。
        </p>
    </li>
</ul>
<p>
    接着在渲染循环前准备“离屏渲染”用到的push constant数据。
    <br>
    用<a href="https://www.glfw.org/docs/latest/group__input.html#ga01d37b6c40133676b9cea60ca1d7c0cc">glfwGetCursorPos</a>(...)获取鼠标指针位置，参数不言自明，得到的是相对于屏幕左上角的坐标，将其结果用作传入着色器的两个点坐标的初始值：
</p>
<pre class="code">
<span class="kw">double</span> mouseX, mouseY;
<span class="fn">glfwGetCursorPos</span>(pWindow, &amp;mouseX, &amp;mouseY);
<span class="kw">struct</span> {
    glm::<span class="type">vec2</span> viewportSize;
    glm::<span class="type">vec2</span> offsets[2];
} pushConstants_offscreen = {
    { canvasSize.width, canvasSize.height },
    { { mouseX, mouseY }, { mouseX, mouseY } }
};
</pre>
<p>
    之后也要不断获取鼠标指针位置，考虑到我们只有两个点，用布尔值作为索引，交替赋值就行了：
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="cmt">/*...前略*/</span>

    <span class="kw">double</span> mouseX, mouseY;
    <span class="fn">glfwGetCursorPos</span>(pWindow, &amp;mouseX, &amp;mouseY);
    <span class="kw">struct</span> {
        glm::<span class="type">vec2</span> viewportSize;
        glm::<span class="type">vec2</span> offsets[2];
    } pushConstants_offscreen = {
        { canvasSize.width, canvasSize.height },
        { { mouseX, mouseY }, { mouseX, mouseY } }
    };

    <span class="kw">bool</span> clearCanvas = <span class="kw">true</span>;
    <span class="cmt">/*新增*/</span><span class="kw">bool</span> index = 0;

    <span class="kw">while</span> (!<span class="fn">glfwWindowShouldClose</span>(pWindow)) {
        <span class="cmt">/*...前略*/</span>

        <span class="fn">glfwPollEvents</span>();

        <span class="cmt">/*新增*/</span><span class="fn">glfwGetCursorPos</span>(pWindow, &amp;mouseX, &amp;mouseY);
        <span class="cmt">/*新增*/</span>pushConstants_offscreen.offsets[index = !index] = { mouseX, mouseY };
        clearCanvas = <span class="fn">glfwGetMouseButton</a>(pWindow, <span class="mcr">GLFW_MOUSE_BUTTON_LEFT</span>)
    }
    <span class="fn">TerminateWindow</span>();
    <span class="kw">return</span> 0;
}
</pre>
<p>
    操作逻辑到此写完，该写离屏部分的渲染代码了，一如既往：开始渲染通道、绑定管线、更新常量、绘制、结束渲染通道
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="cmt">/*...前略*/</span>

    <span class="kw">double</span> mouseX, mouseY;
    <span class="fn">glfwGetCursorPos</span>(pWindow, &amp;mouseX, &amp;mouseY);
    <span class="kw">struct</span> {
        glm::<span class="type">vec2</span> viewportSize;
        glm::<span class="type">vec2</span> offsets[2];
    } pushConstants_offscreen = {
        { canvasSize.width, canvasSize.height },
        { { mouseX, mouseY }, { mouseX, mouseY } }
    };

    <span class="kw">bool</span> clearCanvas = <span class="kw">true</span>;
    <span class="kw">bool</span> index = 0;

    <span class="kw">while</span> (!<span class="fn">glfwWindowShouldClose</span>(pWindow)) {
        <span class="kw">while</span> (<span class="fn">glfwGetWindowAttrib</span>(pWindow, <span class="mcr">GLFW_ICONIFIED</span>))
            <span class="fn">glfwWaitEvents</span>();

        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);
        <span class="kw">auto</span> i = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>();
        commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);

        <span class="kw">if</span> (clearCanvas)
            easyVulkan::<span class="fn">CmdClearCanvas</span>(commandBuffer, <span class="type">VkClearColorValue</span>{}),
            clearCanvas = <span class="kw">false</span>;

        <span class="cmt">//离屏</span>
        renderPass_offscreen.<span class="fn">CmdBegin</span>(commandBuffer, framebuffer_offscreen, { {}, canvasSize });
        <span class="fn">vkCmdBindPipeline</span>(commandBuffer, <span class="enum">VK_PIPELINE_BIND_POINT_GRAPHICS</span>, pipeline_line);
        <span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_line, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 24, &amp;pushConstants_offscreen);
        <span class="fn">vkCmdDraw</span>(commandBuffer, 2, 1, 0, 0);
        renderPass_offscreen.<span class="fn">CmdEnd</span>(commandBuffer);

        <span class="cmt">/*采样到屏幕，待填充*/</span>

        commandBuffer.<span class="fn">End</span>();
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(commandBuffer, semaphore_imageIsAvailable, semaphore_renderingIsOver, fence);
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>(semaphore_renderingIsOver);

        <span class="fn">glfwPollEvents</span>();
        <span class="fn">glfwGetCursorPos</span>(pWindow, &amp;mouseX, &amp;mouseY);
        pushConstants_offscreen.offsets[index = !index] = { mouseX, mouseY };
        clearCanvas = <span class="fn">glfwGetMouseButton</a>(pWindow, <span class="mcr">GLFW_MOUSE_BUTTON_LEFT</span>)
        <span class="fn">TitleFps</span>();

        fence.<span class="fn">WaitAndReset</span>();
    }
    <span class="fn">TerminateWindow</span>();
    <span class="kw">return</span> 0;
}
</pre>
<p>
    采样到屏幕：开始渲染通道、绑定管线、绑定描述符、更新常量、绘制、结束渲染通道
</p>
<pre class="code">
renderPass_screen.<span class="fn">CmdBegin</span>(commandBuffer, framebuffers_screen[i], { {}, windowSize }, <span class="type">VkClearValue</span>{ .color = { 1.f, 1.f, 1.f, 1.f } });
<span class="fn">vkCmdBindPipeline</span>(commandBuffer, <span class="enum">VK_PIPELINE_BIND_POINT_GRAPHICS</span>, pipeline_screen);

<span class="cmt">//VkExtent2D底层是两个uint32_t，得转为float</span>
glm::<span class="type">vec2</span> windowSize</span> = { ::windowSize.width, ::windowSize.height };
<span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_screen, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 8, &amp;windowSize);
<span class="cmt">//pushConstants_offscreen.viewportSize就是canvasSize转到vec2类型</span>
<span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_screen, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span> | <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>, 8, 8, &amp;pushConstants_offscreen.viewportSize);

<span class="fn">vkCmdBindDescriptorSets</span>(commandBuffer, <span class="enum">VK_PIPELINE_BIND_POINT_GRAPHICS</span>, pipelineLayout_screen, 0, 1, descriptorSet_texture.<span class="enum">Address</span>(), 0, <span class="enum">nullptr</span>);
<span class="fn">vkCmdDraw</span>(commandBuffer, 4, 1, 0, 0);
renderPass_screen.<span class="fn">CmdEnd</span>(commandBuffer);
</pre>
<p>
    这里需要注意的是，不同着色器阶段使用的push constant范围重叠时：
    <br>
    更新重叠的部分时要注明涉及的全部着色器阶段，而如果某个范围不会被某个阶段使用，更新该范围时不能注明无关的阶段（将只有某个阶段使用的范围，和重叠的范围分开更新即可）。
</p>
<p>
    下面这种写法不符合规范（验证层会报错，虽然运行未必出错）：
</p>
<pre class="code">
<span class="kw">struct</span> {
    glm::<span class="type">vec2</span> viewportSize;
    glm::<span class="type">vec2</span> canvasSize;
} pushConstants = {
    { windowSize.width, windowSize.height },
    { canvasSize.width, canvasSize.height }
};
<span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_screen, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span> | <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>, 0, 16, &amp;pushConstants);
</pre>
<p>
    运行程序，效果已经在前面书写着色器处展示过了。
</p>
<p>
    。。。你以为事情这么一来就结束了吗？（似曾相识的台词）
</p></section>
<section id="id9">
<h2>写这节时遭遇的硬件差异问题<a class="headerlink" href="#id9" title="Permalink to this heading"></a></h2>
<p>
    书写这节的示例代码的过程中我遇到了一个预料之外的问题。
    <br>
    上面这些代码，一点不改（你可以直接拿示例代码<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/tree/main/solution/EasyVulkan_Ch8/Ch8-1.hpp">Ch8-1.hpp</a>跑跑看），如果你用Intel核显来跑（我的显卡型号在下图中），可能会有这种事：
</p>
<img alt="_images/ch8-1-2.png" src="_images/ch8-1-2.png">
<ul>
    <li>
        <p>
            这是个1280x720的窗口，周围白的也是窗口图像的一部分。
        </p>
    </li>
</ul>
<p>
    简而言之：在“渲染到屏幕”时，没有正确更新push constant中的<span class="var">canvasSize</span>，在其位置上的数据似乎保留为了鼠标指针位置（<span class="path">Line.vert.shader</span>中的<span class="var">offset[0]</span>）。至于为什么连蓝色和绿色的线都消失了？。。。我也不知道！
</p>
<p>
    这个情况只在上述代码中<span class="var">canvasSize</span>和交换链图像的大小<span class="var">windowSize</span>一样时发生。
    <br>
    笔者的怀疑是：英特尔驱动可能有某种方式判断更新的数据是否跟先前的一致，然后决定要不要将更新记录在命令缓冲区，但代码实现有错误。
    <br>
    （我这个解释很可能不对，我以往用Intel核显跑代码时还遇到过一些push constant相关的类似问题，似乎无法以此来简单解释）
</p>
<p>
    仅仅就当前这个情况，在完全符合规范的范畴内的改法，最省事的是这样子的：
</p>
<pre class="code">
<span class="kw">struct</span> {
    glm::<span class="fn">vec2</span> viewportSize;
    glm::<span class="fn">vec2</span> canvasSize;
} pushConstants = {
    { windowSize.width, windowSize.height },
    { canvasSize.width, canvasSize.height }
};
<span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_screen, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span>, 0, 16, &amp;pushConstants);
<span class="cmt">//对于我的两款显卡，有没有下面这行跑出来结果似乎都一样，但根据规范需要有</span>
<span class="fn">vkCmdPushConstants</span>(commandBuffer, pipelineLayout_screen, <span class="enum">VK_SHADER_STAGE_VERTEX_BIT</span> | <span class="enum">VK_SHADER_STAGE_FRAGMENT_BIT</span>, 8, 8, &amp;pushConstants.canvasSize);
</pre>
<ul>
    <li>
        <p>
            你问为什么这么改就好了？因为实验结果告诉我能行！。。。
            <br>
            这样本该理应是发生了重复更新的，而且这只是这个情况下的做法，我不知道管线阶段更多或者push constant重叠范围更复杂时会怎么样及该怎么办。
        </p>
    </li>
</ul>
<p>
    也能这么解决：因为push constant的常量是记录在命令缓冲区中的，只要把“离屏渲染”和“渲染到屏幕”分别录制在不同的命令缓冲区中，前者的push constant就不可能影响到后者。
    <br>
    也能这么解决：不管某个值在某个阶段用不用得到，在所有着色器阶段中声明全部的push constant范围，然后总是一次性更新整个范围（做法是很保险，就是太简单粗暴了）。
    <br>
    还能这么解决：抛弃Vulkan，去用OpenGL！。。。
</p>
<p>
    如果你遇到了类似的情况并且搞清楚了怎么回事的话，可以到<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/issues/7">Github issue</a>里告诉我。
</p>
<p>
    类似的问题在18年有人向英特尔反应过：<a href="https://community.intel.com/t5/Graphics/Vulkan-Push-Constants-doesn-t-work-correctly-on-Windows-Intel/td-p/1176934">Vulkan Push Constants doesn't work correctly on Windows Intel GPU drivers</a>
    <br>
    （显然没有引起官方的重视）
</p></section>
</section>


           </div>
          </div>
          <footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
        <a href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html" class="btn btn-neutral float-left" title="Ch7-7 使用贴图" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> 上一页</a>
        <a href="Ch8-2%20%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%95%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%8F%AF%E8%A7%86%E5%8C%96.html" class="btn btn-neutral float-right" title="Ch8-2 深度测试和深度可视化" accesskey="n" rel="next">下一页 <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
    </div>

  <hr/>

  <div role="contentinfo">
    <p>&#169; 版权所有 2021-2024, Qiao YeCheng.</p>
  </div>

  利用 <a href="https://www.sphinx-doc.org/">Sphinx</a> 构建，使用了 
    <a href="https://github.com/readthedocs/sphinx_rtd_theme">主题</a>
    由 <a href="https://readthedocs.org">Read the Docs</a>开发.
   

</footer>
        </div>
      </div>
    </section>
  </div>
  <script>
      jQuery(function () {
          SphinxRtdTheme.Navigation.enable(true);
      });
  </script> 

</body>
</html>